package mspbots.qbd.desktop.core;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.SecureUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intuit.developer.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mspbots.qbd.common.RabbitKeys;
import mspbots.qbd.desktop.core.response.*;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import static mspbots.qbd.common.RedisKeys.TENANT_FILE_PATH_REDIS_KEY;

/**
 * @author AlexBob
 */
@Log4j2
@Endpoint
@RequiredArgsConstructor
public class QbdSoapEndpoint {

    private static final String NAMESPACE_URI = "http://developer.intuit.com/";

    private final RabbitMessagingTemplate rabbitMessagingTemplate;
    private final StringRedisTemplate stringRedisTemplate;
    private final ObjectMapper objectMapper;

    private final RequestXmlService loadQbXmlService;
    private final ResponseResultService resultResponseService;

    private final Map<String, SoapSession> sessions = new HashMap<>(10);

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "authenticate")
    @ResponsePayload
    public AuthenticateResponse authenticate(@RequestPayload Authenticate authenticate) {
        String strUserName = authenticate.getStrUserName(), strPassword = authenticate.getStrPassword();
        UUID ticket = UUID.randomUUID();
        String[] asRtn = new String[4];
        asRtn[0] = ticket.toString();
        if ("mspbots".equals(strUserName) && strPassword != null) {
            String tenantIdStr = SecureUtil.des(WebServiceConfig.AUTH_DES_SECRET_KEY.getBytes())
                    .decryptStr(strPassword, CharsetUtil.CHARSET_UTF_8);
            SoapSession soapSession = SoapSession.withDefault(Long.parseLong(tenantIdStr), "authenticate");
            String path = this.stringRedisTemplate.opsForValue().get(TENANT_FILE_PATH_REDIS_KEY + tenantIdStr);
            if (StringUtils.isEmpty(path)) {
                path = "";
            }
            asRtn[1] = path;
            soapSession.getResultSync().setMsg("Authenticate is success.");
            soapSession.getResultSync().setParams(this.objectMapper.createObjectNode().put("strUserName", strUserName)
                    .put("tenantId", soapSession.getTenantId())
                    .put("path", asRtn[1]).put("ticket", asRtn[0]));

            log.debug("In authenticate ticket = {}", asRtn[0]);
            log.debug("In authenticate path = {}", asRtn[1]);
            log.debug("In authenticate tenantId = {}", soapSession.getTenantId());

            soapSession.getResultSync().setRequestId(ticket);
            soapSession.getResultSync().setElapsedTime(System.currentTimeMillis() - soapSession.getResultSync().getElapsedTime());
            this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                    RabbitKeys.LOGS_TASK_QUEUE_KEY, soapSession.getResultSync());

            this.sessions.put(asRtn[0], soapSession);
        } else {
            asRtn[1] = "none";
        }

        AuthenticateResponse.ArrayOfString arrayOfString = new AuthenticateResponse.ArrayOfString();
        arrayOfString.setString(asRtn);
        AuthenticateResponse authenticateResponse = new AuthenticateResponse();
        authenticateResponse.setAuthenticateResult(arrayOfString);
        return authenticateResponse;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "sendRequestXML")
    @ResponsePayload
    public SendRequestXMLResponse sendRequestXml(@RequestPayload SendRequestXML sendRequestXML) {
        SoapSession soapSession = this.sessions.get(sendRequestXML.getTicket());
        log.debug("sendRequestXML ticket session id {}", sendRequestXML.getTicket());
        soapSession.getResultSync().setElapsedTime(System.currentTimeMillis());

        String requestXml = this.loadQbXmlService.requestXml(soapSession);
        log.info("sendRequestXML content:\n {}", requestXml);

        soapSession.getResultSync().setMsg("Read and send request xml success.");
        soapSession.getResultSync().setCount(requestXml.getBytes().length);
        soapSession.getResultSync().setParams(this.objectMapper.createObjectNode().put("ticket", sendRequestXML.getTicket())
                .put("requestXml", requestXml));
        soapSession.getResultSync().setAction("sendRequestXml");
        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, soapSession.getResultSync());

        this.sessions.put(sendRequestXML.getTicket(), soapSession);

        SendRequestXMLResponse xmlResponse = new SendRequestXMLResponse();
        xmlResponse.setSendRequestXMLResult(requestXml);
        return xmlResponse;
    }


    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "receiveResponseXML")
    @ResponsePayload
    public ReceiveResponseXMLResponse receiveResponseXml(@RequestPayload ReceiveResponseXML receiveResponseXML) {
        SoapSession session = this.sessions.get(receiveResponseXML.getTicket());
        log.debug("receiveResponseXml ticket {}", receiveResponseXML.getTicket());

        try {
            log.debug("Receive response XML:\n {}", receiveResponseXML.getResponse());
            this.resultResponseService.buildResult(receiveResponseXML.getResponse(), session);
        } catch (Exception e) {
            log.error("Read xml error. msg: {}", e.getMessage());
            return new ReceiveResponseXMLResponse().setReceiveResponseXMLResult(100);
        }

        if (session.getIndex() > RequestDataType.REQUEST_DATA_TYPE_COUNT - 1) {
            session.setIndex(100);
        }

        int result = session.getIndex();
        session.getResultSync().setMsg("This ticket receive response XML " + receiveResponseXML.getTicket() + ", result index [" + result + "]");
        session.getResultSync().setParams(this.objectMapper.createObjectNode().put("ticket", receiveResponseXML.getTicket())
                .put("receiveXml", receiveResponseXML.getResponse()));
        session.getResultSync().setCount(receiveResponseXML.getResponse().getBytes().length);
        session.getResultSync().setAction("receiveResponseXML");
        session.getResultSync().setElapsedTime(System.currentTimeMillis() - session.getResultSync().getElapsedTime());
        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, session.getResultSync());
        this.sessions.put(receiveResponseXML.getTicket(), session);

        return new ReceiveResponseXMLResponse().setReceiveResponseXMLResult(result);
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getLastError")
    @ResponsePayload
    public GetLastErrorResponse getLastError(@RequestPayload GetLastError getLastError) {
        SoapSession session = this.sessions.get(getLastError.getTicket());
        log.debug("getLastError ticket {}", getLastError.getTicket());

        session.getResultSync().setMsg("This ticket " + getLastError.getTicket() + ", Error index [" + session.getIndex() + "]");
        session.getResultSync().setAction("getLastError");
        session.getResultSync().status(HttpStatus.INTERNAL_SERVER_ERROR);
        session.getResultSync().setElapsedTime(System.currentTimeMillis() - session.getResultSync().getElapsedTime());
        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, session.getResultSync());

        this.sessions.remove(getLastError.getTicket());

        log.error("getLastError ticket session {}", session);
        GetLastErrorResponse getLastErrorResponse = new GetLastErrorResponse();

        getLastErrorResponse.setGetLastErrorResult("The server experienced an unpredictable error and will try again later.");
        return getLastErrorResponse;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "connectionError")
    @ResponsePayload
    public ConnectionErrorResponse connectionError(@RequestPayload ConnectionError connectionError) {
        SoapSession session = this.sessions.get(connectionError.getTicket());
        log.error("connectionError ticket {}, Msg: {}", connectionError.getTicket(), connectionError.getMessage());

        session.getResultSync().setMsg(connectionError.getMessage());
        session.getResultSync().setParams(this.objectMapper.convertValue(connectionError, JsonNode.class));
        session.getResultSync().setAction("connectionError");
        session.getResultSync().status(HttpStatus.INTERNAL_SERVER_ERROR);
        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, session.getResultSync());

        ConnectionErrorResponse connectionErrorResponse = new ConnectionErrorResponse();
        connectionErrorResponse.setConnectionErrorResult("");
        return connectionErrorResponse;
    }


    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "closeConnection")
    @ResponsePayload
    public CloseConnectionResponse closeConnection(@RequestPayload CloseConnection closeConnection) {
        SoapSession session = this.sessions.get(closeConnection.getTicket());
        session.getResultSync().setElapsedTime(System.currentTimeMillis());
        log.error("closeConnection ticket {}", closeConnection.getTicket());

        session.getResultSync().setMsg("This ticket " + closeConnection.getTicket() + " close connection,index [" + session.getIndex() + "]");
        session.getResultSync().setAction("closeConnection");
        session.getResultSync().status(HttpStatus.INTERNAL_SERVER_ERROR);
        session.getResultSync().setElapsedTime(System.currentTimeMillis() - session.getResultSync().getElapsedTime());
        this.rabbitMessagingTemplate.convertAndSend(RabbitKeys.BASE_EXCHANGE_NAME,
                RabbitKeys.LOGS_TASK_QUEUE_KEY, session.getResultSync());

        this.sessions.remove(closeConnection.getTicket());
        CloseConnectionResponse connectionResponse = new CloseConnectionResponse();
        connectionResponse.setCloseConnectionResult("O:This " + closeConnection.getTicket() + " close success. ");
        return connectionResponse;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "serverVersion")
    @ResponsePayload
    public ServerVersionResponse serverVersion() {
        log.debug("serverVersion O:2.3.0.36");
        ServerVersionResponse serverVersionResponse = new ServerVersionResponse();
        serverVersionResponse.setString("O:2.3.0.36");
        return serverVersionResponse;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "clientVersion")
    @ResponsePayload
    public ClientVersionResponse clientVersion(@RequestPayload ClientVersion clientVersion) {
        log.debug("clientVersion clientVersion {}", clientVersion.getStrVersion());
        String retVal = clientVersion.getStrVersion();
        double recommendedVersion = 2.3;
        double supportedMinVersion = 2.2;
        String[] versionArray = StringUtils.delimitedListToStringArray(retVal, ".");
        double suppliedVersion = Double.parseDouble(versionArray[0] + "." + versionArray[1]);
        if (suppliedVersion < supportedMinVersion) {
            retVal = "E:You need to upgrade your QBWebConnector";
        } else if (suppliedVersion < recommendedVersion) {
            retVal = "W:We recommend that you upgrade your QBWebConnector";
        } else {
            retVal = "O:" + retVal;
        }
        ClientVersionResponse clientVersionResponse = new ClientVersionResponse();
        clientVersionResponse.setString(retVal);
        return clientVersionResponse;
    }

}
